/**
 * Copyright (c) 2010 Simple Graph Tools contributors. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id$
 */
package org.sgt.coredef.util;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.sgt.coredef.CoredefPackage;
import org.sgt.coredef.DescribedObject;
import org.sgt.coredef.Proxy;

/**
 * 
 * the behavioral code for a {@link Proxy}.
 * 
 * @author BUTTIGHOFFER-A
 * 
 */
public final class ProxyHelper {

	/**
	 * Prefix : "Proxy for '".
	 */
	public static final String PROXY_PREFIX = "Proxy for '";

	/**
	 * Suffix : "'".
	 */
	public static final String PROXY_SUFFFIX = "'";

	/**
	 * Summary : "Proxy (see representation for description)".
	 */
	public static final String PROXY_SUMMARY 
		= "Proxy (see representation for description)";

	/**
	 * Name of the ID feature.
	 */
	public static final String ID_NAME = CoredefPackage.eINSTANCE
			.getDescribedObject_ID().getName();

	/**
	 * Name of the represented objects feature.
	 */
	public static final String REPRESENTEDOBJECT_NAME = CoredefPackage.eINSTANCE
			.getProxy_RepresentedObject().getName();

	/**
	 * Helper Class has a private constructor.
	 */
	private ProxyHelper() {
		super();
	}

	/**
	 * Determines whether the {@link Proxy} is in sync with its
	 * representedObject.
	 * @param proxy {@link Proxy}.
	 * @param featuresList {@link EList<EStructuralFeature>}.
	 * @return true only if the synchronization is correct.
	 */
	public static boolean isSynchronised(final Proxy<?> proxy
			, final EList<EStructuralFeature> featuresList) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * This default implementation synchronizes <B>all features of the
	 * featuresList</B> with the following convention :
	 * <UL>
	 * <LI>name is set to "Proxy for 
	 * 'proxy.getRepresentedObject.getName()'"</LI>
	 * <LI>summary is set to "Proxy (see representation summary for
	 * description)"</LI>
	 * <LI>ID is not changed</LI>
	 * <LI>representedObject of a proxy is not changed</LI>
	 * <LI>all other existing features are copied from 
	 * representation to proxy.</LI>
	 * <LI>a null or empty {@link #featuresList} means all existing
	 * representation features.</LI>
	 * </UL>
	 * 
	 * @param proxy
	 *            the {@link Proxy} to be synchronized.
	 * @param featuresList
	 *            the {@link EObject} represented Object's features list to be
	 *            the sources of the {@link Proxy} synchronization.
	 * @return true only if synchronization was successful.
	 */
	public static boolean syncProxy(final Proxy<?> proxy,
			final EList<EStructuralFeature> featuresList) {

		EList<EAttribute> toSyncAttrList = buildAttributeList(proxy,
				featuresList);
		EList<EReference> toSyncRefList = buildReferenceList(proxy,
				featuresList);

		EClass proxyEClass = proxy.eClass();
		EObject eObjRep = (EObject) proxy.getRepresentedObject();

		boolean ok = false;
		if (!toSyncAttrList.isEmpty() || !toSyncRefList.isEmpty()) {
			EList<EAttribute> allproxAttList = proxyEClass.getEAllAttributes();
			EList<EReference> allproxRefList = proxyEClass.getEAllReferences();
			for (EAttribute eAttr : toSyncAttrList) {
				if (allproxAttList.contains(eAttr)) {
					proxy.eSet(eAttr, eObjRep.eGet(eAttr));
				}
				if (eObjRep instanceof DescribedObject) {
					// Setting name...
					StringBuffer buffer = new StringBuffer();
					buffer.append(PROXY_PREFIX);
					buffer.append(((DescribedObject) eObjRep).getName());
					buffer.append(PROXY_SUFFFIX);
					buffer.append("@" + proxy.hashCode());
					proxy.setName(buffer.toString());
					// Setting summary...
					proxy.setSummary(PROXY_SUMMARY);
				}
			}
			boolean eAttrOK = true;
			for (EReference eRef : toSyncRefList) {
				if (allproxRefList.contains(eRef)) {
					proxy.eSet(eRef, eObjRep.eGet(eRef));
				}
			}
			boolean eRefOK = true;
			ok = eAttrOK && eRefOK;
		}
		return ok;
	}

	/**
	 * Synchronizes the representation values from the ones in the proxy. <br/>
	 * <em>!!! This default implementation still does nothing
	 * and returns false. !!!</em>
	 * 
	 * @param proxy
	 *            the {@link Proxy} to synchronize values from.
	 * @param featuresList
	 *            the {@link Proxy} features list to be the sources of
	 *            synchronization of the representation.
	 * @return true only if synchronization was successful.
	 */
	public static boolean syncRepresentation(final Proxy<?> proxy,
			final EList<EStructuralFeature> featuresList) {
		return false;
	}

	/**
	 * Builds the list of {@link EAttribute} to synchronize from this proxy's
	 * represented object and the given features list.
	 * 
	 * @param proxy
	 *            {@link Proxy<?>}
	 * @param featuresList
	 *            {@link EList<EStructuralFeature>}.
	 * @return the attributes list to synchronize.
	 */
	protected static EList<EAttribute> buildAttributeList(final Proxy<?> proxy,
			final EList<EStructuralFeature> featuresList) {
		checkingProxy(proxy);

		// checking or building synch list...
		EObject eObjRep = (EObject) proxy.getRepresentedObject();
		EClass repEClass = eObjRep.eClass();
		EList<EStructuralFeature> toSyncList 
			= new BasicEList<EStructuralFeature>();
		EList<EAttribute> toSyncAttrList = new BasicEList<EAttribute>();
		EList<EAttribute> allRepAttList = repEClass.getEAllAttributes();

		if (featuresList == null || featuresList.isEmpty()) {
			toSyncAttrList.addAll(allRepAttList);
		} else {
			toSyncList.addAll(featuresList);
		}

		for (EStructuralFeature feature : toSyncList) {
			if (feature instanceof EAttribute) {
				if (allRepAttList.contains((EAttribute) feature)) {
					EAttribute eAttr = (EAttribute) feature;
					// filtering out what should not be changed...
					if (!eAttr.isDerived() && eAttr.isChangeable()
							&& !eAttr.isID()
							// for now ID attr is not set to identifiant
							// in ecore model...
							&& eAttr.getName().compareTo(ID_NAME) != 0) {
						toSyncAttrList.add(eAttr);
					}
				}
			}
		}
		return toSyncAttrList;
	}

	/**
	 * Builds the list of {@link EReferences} to synchronize from this proxy's
	 * represented object and the given features list.
	 * 
	 * @param proxy
	 *            {@link Proxy<?>}
	 * @param featuresList
	 *            {@link EList<EStructuralFeature>}.
	 * @return the references list to synchronize.
	 */
	protected static EList<EReference> buildReferenceList(final Proxy<?> proxy,
			final EList<EStructuralFeature> featuresList) {
		checkingProxy(proxy);

		// checking or building synch list...
		EObject eObjRep = (EObject) proxy.getRepresentedObject();
		EClass repEClass = eObjRep.eClass();
		EList<EStructuralFeature> toSyncList
			= new BasicEList<EStructuralFeature>();
		EList<EReference> toSyncRefList = new BasicEList<EReference>();
		EList<EReference> allRepRefList = repEClass.getEAllReferences();

		if (featuresList == null || featuresList.isEmpty()) {
			toSyncRefList.addAll(allRepRefList);
		} else {
			toSyncList.addAll(featuresList);
		}

		for (EStructuralFeature feature : toSyncList) {
			if (feature instanceof EReference) {
				if (!allRepRefList.contains((EReference) feature)) {
					throw new UnsupportedOperationException(
							"proxy representation does not contain all"
									+ "sync features");
				}
				EReference eRef = (EReference) feature;
				// filtering out what should not be changed...
				if (eRef.isChangeable()
						&& !eRef.isDerived()
						&& eRef.getEOpposite() == null
						&& !eRef.isContainment()
						&& eRef.getName()
							.compareTo(REPRESENTEDOBJECT_NAME) != 0) {
					toSyncRefList.add(eRef);
				}
			}
		}
		return toSyncRefList;
	}

	/**
	 * Checks that our proxy points to a non null and EObject instance
	 * representation. Throws an {@link UnsupportedOperationException}
	 * otherwise.
	 * 
	 * @param proxy
	 *            {@link Proxy}.
	 */
	public static void checkingProxy(final Proxy<?> proxy) {
		// Making sure the proxy is representing a non null EObject...
		if (proxy == null) {
			throw new UnsupportedOperationException("proxy is null");
		}
		if (!(proxy.getRepresentedObject() instanceof EObject)) {
			throw new UnsupportedOperationException("proxy representation"
					+ " is not an EObject");
		}
	}
}
